Using PLAY_VOICE

This document explains how to use my new song playing routines, as used in 
SONGDEMO.BIN and posted here previously.

The Basics
===========
Song playing is done through the use of four routines. The first, is 
setup_intro_mus. It is called just once, and initialises all the variables 
for use in playing the song. setup_intro_mus is actually a completely 
arbitrary name. In fact, if you have several tunes to be played throught the 
course of your program you will need to setup many of the song playing 
variables each time. So, you will need multiple setup_intro_mus (though 
obviously not all called that). Or, you can just integrate setup_intro_mus' 
functions into another initialisation routine. setup_intro_mus (or something 
like it) must be called first, before proceeding with actual song playing.

The real work is done by the play_voice1, play_voice2 and play_voice3 
routines. Each one of the routines corresponds to a specific voice. 
play_voice1 plays a tune on voice 1. play_voice 2 plays a tune on voice 2 and 
play_voice3 plays a voice on voice3. All three routines are completely 
independant and you only need to call the routines you need. So, if you want 
to play a tune ONLY on voice 1, only use play_voice1 and don't use 
play_voice2 or play_voice3.

play_voice1, play_voice2 and play_voice3 should be called immediately after 
waitrecal in any main_loop that needs to play a song.


Note Lengths
============
play_voice uses whole, half, quarter, eight and sixteenth notes. The lengths 
of these notes are defined by a series of variables as follows:

whole_dur      - Duration of whole note
half_dur       - Duration of half note
quarter_dur    - Duration of quarter note
eighth_dur     - Duration of eight note
sixteenth_dur  - Duration of sixteenth note

The contents of these variables determines hoe many iterations of the 
main_loop the note will be held. In the case of waitrecal's default timer 
setting, the main_loop is executed 50 times per second. So each iteration is 
1/50th of a second. The values I've used are:

whole_dur      - 32
half_dur       - 16
quarter_dur    -  8
eighth_dur     -  4
sixteenth_dur  -  2

These values are arbitrary and you can change them at your leasure. These 
values mean that a whole note is twice as long as a half note, a half note 
twice as long as a quarter, a quarter twice as long as an eighth and and 
eighth twice as long as a sixteenth. A whole note is held for 32/50 of a 
second.

These ratios do not need to be maintained, either. If you need extra long 
notes or you don't need the very short notes, or you want notes of (say) 3/4 
the length of other notes these values may be changed to reflect these needs. 
The name of the variables simply reflect what seemed their most likely use 
and doesn't need to reflect the actual note lengths.

Just be sure to modify the ADSR envelopes to reflect note lengths.


ADSR Envelopes
==============

Each note length has it's own ADSR envelope for each voice. This is so that 
notes of different lengths can be scaled properly to sound uniform at 
different durations without the Vectrex having to do the work of 
interpolating of exterpolating the ADSR envelopes. It's not as elegant a 
solution as letting the AY-3-8192 do the work and this may change in later 
versions of these routines as I come to understand on ADSR envelopes work on 
the AY-3-8192.

The addresses of the ADSR tables are stored in a series of variables. These 
variables are named:

V1_whole     - Address of voice 1 whole note ADSR table
V1_half      - Address of voice 1 half note ADSR table
V1_quarter   - Address of voice 1 quarter note ADSR table
V1_eighth    - Address of voice 1 quarter note ADSR table
V1_sixteenth - Address of voice 1 sixteenth note ADSR table
V2_whole
V2_half
V2_quarter
V2_eighth
V2_sixteenth
V3_whole
V3_half
V3_quarter
V3_eighth
V3_sixteenth

setup_intro_mus sets these variables as follows, however, you can use tables 
of your own name and design. Or just overwrite and modify these until you 
gett used to it.

ins1_w_ADSR: Voice 1 whole note ADSR table
ins1_h_ADSR: Voice 1 half note ADSR table
ins1_q_ADSR: Voice 1 quarter note ADSR table
ins1_e_ADSR: Voice 1 eight note ADSR table
ins1_s_ADSR: Voice 1 sixteenth note ADSR table

ins2_w_ADSR: 
ins2_h_ADSR: 
ins2_q_ADSR: 
ins2_e_ADSR: 
ins2_s_ADSR: 
   
ins3_w_ADSR: 
ins3_h_ADSR: 
ins3_q_ADSR: 
ins3_e_ADSR: 
ins3_s_ADSR: 

Each value in the table is the actual volume the note will play at for that 
itteration through the main_loop. Since with waitrecal the main_loop executes 
50 imes per second, you have control of the wave envelope at 1/50 of a second 
intervals. Not perfect, but good enough to give you pretty good control of 
the sound.

The ADSR tables actually work from high memory to low (or right to left if 
you put all the values on one line as I try to do).

For example, a table like:

ins1_h_ADSR: 15,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1
             ^^                                   ^
             Final Volume                         First Volume

Ramps up the volume of the note from silence to full volume. The note is then 
cut off quickly at the end as it goes from full volume to off.

Meanwhile:

ins1_h_ADSR: 1,3,5,7,9,11,13,15,15,15,15,15,15,11,6,1
             ^^                                   ^
             Final Volume                         First Volume

This ADSR ramps volume up to full quickly, hold full volume and then fades 
it back down again fairly slowly.

By using varying ADSR envelopes, you can create different sounding 
instruments for each voice to play as each voice has it's own set of ADSR 
envelopes, one for every note length.

If you're going to be using the voice's ADSR envelopes to creates notes of 
the same instrument of different length (You don't HAVE to do this. You could 
use completely different ADSR envelopes on the same voice thus giving you 
access to different instrumenst on a single voice) you need to scale the 
envelopes accordingly.

For example:


   lda #16
   sta whole_dur
   lda #8
   sta half_dur
   lda #ins1_w_ADSR
   sta V1_whole
   lda #ins1_h_ADSR
   sta V1_half
   rts

ins1_w_ADSR: 2,5,8,11,11,11,11,11,11,11,11,15,11,6,1
ins1_h_ADSR: 2,5,8,11,15,11,6,1

In this example, voice one has a whole note of duration 16 and a half note of 
duration 8. These notes are scaled, and the half note will sound like a 
shorter version of the whole note. These notes create an ADSR envelope which 
looks like this:

       /\
      /  \
     /     ---------
    /                \
   /                   \
   
The initial rising volume is the ATTACK which increases to the maximum 
volume. Then the volume DECAYS until it reaches the SUSTAIN volume. This 
volume is held until the RELEASE begin and the sound fades to off.

This is a typical ADSR envelope. When scaling, you generally want to start 
with the shortest note. To scale up to longer notes, increase the length of 
the SUSTAIN.

You don't HAVE to use the standard ADSR type envelopes, you can do any kind 
of oddball ADSR envelopes you want. You can do notes which rise and fall in 
volume or other unusual effects.


Notes
=====

Each play_voice routine uses its own table of notes. When initialising your 
song, store the address of the tables in V1_notes and V1_pos for voice 1. In 
V2_notes and V2_pos for voice 2 and V3_notes and V3_pos for voice 3. 

setup_intro_mus initialises these tables as being at intro_song1:, 
intro_song2: and intro_song3: but you can put the anywhere you like.

Each note is two bytes long. The first byte is the actual note, the second 
byte is the duration of the note. For the note, check Appendix F of Chris 
(The other one's) Vectrex tutorial for the list of note values. The note 
is a whole note if the second byte is a 0, a half note if it's a 1, a quarter 
note if it's a 2, and eighth note if it's a 3 and a sixteenth note if it's 
anything else.

So:

intro_song: fcb #$00,#$01

Will play a half note and it will be a G2.

The end of a song is denoted by a #$ff. A #128 in the first byte indicates 
that that note is a rest and no note (silence) is played for that duration.

In addition, you can play noise. If bit 6 of the first byte is set then noise 
is played. Noise has a frequency of #$00 to #$1f. So noise nottes have a 
value from #$40 to #$5f. Noise notes do not use the note frequency table that 
regular notes do. What's more, changing the frequency doesn't seem to have 
any effect on the actual noise sound produced so you're on your own here.



That's really all there is to it. Take a look at the song demo I've 
provided. It should be pretty clear what's going on. At first all you really
need to play with are the note tables and the ADSR envelopes. Even the ADSR
envelopes are optional if you just want to get something going. After that
you can play with note lengths and such.

As always, feel free to foreward your comments and complaints. I'll be 
around...


Tomorrow: Omega Chase!

###
1

